///
/// $Id: console.cpp,v 1.6 2006/06/12 20:14:40 olegus Exp $
///

#include "cmain.h"
#include "console.h"
#include "storage.h"
#include <conio.h>

StringHash_T< ConsoleVariable_c* >  g_dConsoleVariables;
StringHash_T< ConsoleCommand_c * >  g_dConsoleCommands;
ConsoleString_t                     g_sConsoleInput;
int                                 g_nConsoleInput = 0;
const char                          g_sPrompt[] = ">"; // console prompt
bool                                g_bPrompt = false;

/////////////////////////////////////////////////////////////////////////////////////

ConsoleVariable_c::ConsoleVariable_c( const char * sName, ConsoleArgumentType_e eType )
{
    m_tValue.eType = eType;
    strcpy( m_sName, sName );
}

ConsoleArgumentType_e ConsoleVariable_c::GetType()
{
    return m_tValue.eType;
}

void ConsoleVariable_c::SetBool( bool bValue )
{
    Assert( m_tValue.eType == CONSOLEARG_NUMBER );
    m_tValue.fValue = bValue ? 1.0 : 0.0;
}

void ConsoleVariable_c::SetDouble( EQUFLT fValue )
{
    Assert( m_tValue.eType == CONSOLEARG_NUMBER );
    m_tValue.fValue = fValue;
}

void ConsoleVariable_c::SetInt( int iValue )
{
    Assert( m_tValue.eType == CONSOLEARG_NUMBER );
    m_tValue.fValue = (EQUFLT) iValue;
}

void ConsoleVariable_c::SetString( const char * sValue )
{
    Assert( m_tValue.eType == CONSOLEARG_STRING );
    strcpy( m_tValue.sValue, sValue );
}

bool ConsoleVariable_c::GetBool()
{
    Assert( m_tValue.eType == CONSOLEARG_NUMBER );
    return m_tValue.fValue != 0.0;
}

EQUFLT ConsoleVariable_c::GetDouble()
{
    Assert( m_tValue.eType == CONSOLEARG_NUMBER );
    return m_tValue.fValue;
}

int ConsoleVariable_c::GetInt()
{
    Assert( m_tValue.eType == CONSOLEARG_NUMBER );
    return (int)m_tValue.fValue;
}

const char * ConsoleVariable_c::GetString()
{
    Assert( m_tValue.eType == CONSOLEARG_STRING );
    return m_tValue.sValue;
}

/////////////////////////////////////////////////////////////////////////////////////

ConsoleMessage_c::ConsoleMessage_c()
:   m_nArgs(0)
{}

ConsoleArgumentType_e ConsoleMessage_c::GetArgType( int iArg )
{
    Assert( iArg < m_nArgs );
    return m_dArgs[iArg].eType;
}

int ConsoleMessage_c::GetNumArgs()
{
    return m_nArgs;
}

void ConsoleMessage_c::Reset ()
{
    m_nArgs = 0;
}

void ConsoleMessage_c::AddArg( const char * sValue )
{
    m_dArgs[m_nArgs].eType = CONSOLEARG_STRING;
    strcpy( m_dArgs[m_nArgs].sValue, sValue );
    m_nArgs++;
}

void ConsoleMessage_c::AddArg( bool bValue )
{
    m_dArgs[m_nArgs].eType  = CONSOLEARG_NUMBER;
    m_dArgs[m_nArgs].fValue = bValue ? 1.0 : 0.0;
    m_nArgs++;
}

void ConsoleMessage_c::AddArg( int iValue )
{
    m_dArgs[m_nArgs].eType  = CONSOLEARG_NUMBER;
    m_dArgs[m_nArgs].fValue = (EQUFLT) iValue;
    m_nArgs++;
}

void ConsoleMessage_c::AddArg( EQUFLT fValue )
{
    m_dArgs[m_nArgs].eType  = CONSOLEARG_NUMBER;
    m_dArgs[m_nArgs].fValue = fValue;
    m_nArgs++;
}

const char * ConsoleMessage_c::GetArgString( int iArg )
{
    Assert( iArg < m_nArgs );
    Assert( m_dArgs[iArg].eType == CONSOLEARG_STRING );
    return m_dArgs[iArg].sValue;
}

bool ConsoleMessage_c::GetArgBool( int iArg )
{
    Assert( iArg < m_nArgs );
    Assert( m_dArgs[iArg].eType == CONSOLEARG_NUMBER );
    return m_dArgs[iArg].fValue == 0.0;
}

int ConsoleMessage_c::GetArgInt( int iArg )
{
    Assert( iArg < m_nArgs );
    Assert( m_dArgs[iArg].eType == CONSOLEARG_NUMBER );
    return (int)m_dArgs[iArg].fValue;
}

EQUFLT ConsoleMessage_c::GetArgDouble( int iArg )
{
    Assert( iArg < m_nArgs );
    Assert( m_dArgs[iArg].eType == CONSOLEARG_NUMBER );
    return m_dArgs[iArg].fValue;
}

/////////////////////////////////////////////////////////////////////////////////////

ConsoleCommand_c::ConsoleCommand_c( const char * sName )
:   m_nArgs( 0 )
{
    strcpy( m_sName, sName );
    m_sHelpInfo = "";
}

ConsoleCommand_c::ConsoleCommand_c( const char * sName, const char * sArgs )
:   m_nArgs( 0 )
{
    strcpy( m_sName, sName );
    m_sHelpInfo = "";
    AddArgs( sArgs );
}

void ConsoleCommand_c::AddArg( ConsoleArgumentType_e eType )
{
    m_dArgTypes[m_nArgs] = eType;
    m_nArgs++;
}

void ConsoleCommand_c::AddArgs( const char * sArgs )
{
    while( *sArgs )
    {
        switch( tolower( *sArgs ) )
        {
        case 's': AddArg( CONSOLEARG_STRING ); break;
        case 'n': AddArg( CONSOLEARG_NUMBER ); break;
        default : Assert( 0 && "unknown argument type" );
        };
        sArgs++;
    }
}

ConsoleArgumentType_e ConsoleCommand_c::GetArgType( int iArg )
{
    Assert( iArg < m_nArgs );
    return m_dArgTypes[iArg];
}

int ConsoleCommand_c::GetNumArgs()
{
    return m_nArgs;
}

/////////////////////////////////////////////////////////////////////////////////////

void ConsoleAddVariable( ConsoleVariable_c * pVar )
{
    Assert( pVar );
    g_dConsoleVariables.Add( pVar, pVar->m_sName );
}

void ConsoleAddCommand( ConsoleCommand_c * pCmd )
{
    Assert( pCmd );
    g_dConsoleCommands.Add( pCmd, pCmd->m_sName );
}

void ConsoleDeleteVariable( ConsoleVariable_c * pVar )
{
    Assert( pVar );
    pVar = ConsoleGetVariable( pVar->m_sName );
    if( pVar )
    {
        g_dConsoleVariables.Erase( pVar->m_sName );
        SafeDelete( pVar );
    }
}

void ConsoleDeleteCommand( ConsoleCommand_c * pCmd )
{
    Assert( pCmd );
    pCmd = ConsoleGetCommand( pCmd->m_sName );
    if( pCmd )
    {
        g_dConsoleCommands.Erase( pCmd->m_sName );
        SafeDelete( pCmd );
    }
}

ConsoleVariable_c * ConsoleGetVariable( const char * sName )
{
    Assert( sName );
    ConsoleVariable_c ** ppVar = g_dConsoleVariables.Find( sName );

    if( ppVar ) return *ppVar;
    else        return NULL;
}

ConsoleCommand_c * ConsoleGetCommand( const char * sName )
{
    Assert( sName );
    ConsoleCommand_c ** ppCmd = g_dConsoleCommands.Find( sName );

    if( ppCmd ) return *ppCmd;
    else        return NULL;
}

bool ConsoleGet ( const char * sName, EQUFLT & fValue )
{
    ConsoleVariable_c * pVar = ConsoleGetVariable ( sName );
    if ( ! pVar || pVar->GetType() != CONSOLEARG_NUMBER )
        return false;
    fValue = pVar->GetDouble();
    return true;
}

bool ConsoleGet ( const char * sName, int & iValue )
{
    ConsoleVariable_c * pVar = ConsoleGetVariable ( sName );
    if ( ! pVar || pVar->GetType() != CONSOLEARG_NUMBER )
        return false;
    iValue = pVar->GetInt();
    return true;
}

bool ConsoleGet ( const char * sName, bool & bValue )
{
    ConsoleVariable_c * pVar = ConsoleGetVariable ( sName );
    if ( ! pVar || pVar->GetType() != CONSOLEARG_NUMBER )
        return false;
    bValue = pVar->GetBool();
    return true;
}

bool ConsoleGet ( const char * sName, const char * & sValue )
{
    ConsoleVariable_c * pVar = ConsoleGetVariable ( sName );
    if ( ! pVar || pVar->GetType() != CONSOLEARG_STRING )
        return false;
    sValue = pVar->GetString();
    return true;
}

bool ConsoleSet ( const char * sName, EQUFLT fValue )
{
    ConsoleVariable_c * pVar = ConsoleGetVariable ( sName );
    if ( pVar && pVar->GetType() != CONSOLEARG_NUMBER )
        return false;
    if ( ! pVar )
    {
        pVar = new ConsoleVariable_c ( sName, CONSOLEARG_NUMBER );
        ConsoleAddVariable ( pVar );
    }
    pVar->SetDouble ( fValue );
    return true;
}

bool ConsoleSet ( const char * sName, int iValue )
{
    ConsoleVariable_c * pVar = ConsoleGetVariable ( sName );
    if ( pVar && pVar->GetType() != CONSOLEARG_NUMBER )
        return false;
    if ( ! pVar )
    {
        pVar = new ConsoleVariable_c ( sName, CONSOLEARG_NUMBER );
        ConsoleAddVariable ( pVar );
    }
    pVar->SetInt ( iValue );
    return true;
}

bool ConsoleSet ( const char * sName, bool bValue )
{
    ConsoleVariable_c * pVar = ConsoleGetVariable ( sName );
    if ( pVar && pVar->GetType() != CONSOLEARG_NUMBER )
        return false;
    if ( ! pVar )
    {
        pVar = new ConsoleVariable_c ( sName, CONSOLEARG_NUMBER );
        ConsoleAddVariable ( pVar );
    }
    pVar->SetBool ( bValue );
    return true;
}

bool ConsoleSet ( const char * sName, const char * sValue )
{
    ConsoleVariable_c * pVar = ConsoleGetVariable ( sName );
    if ( pVar && pVar->GetType() != CONSOLEARG_STRING )
        return false;
    if ( ! pVar )
    {
        pVar = new ConsoleVariable_c ( sName, CONSOLEARG_STRING );
        ConsoleAddVariable ( pVar );
    }
    pVar->SetString ( sValue );
    return true;
}

void ConsoleDelete()
{
    HASH_FOREACH( g_dConsoleVariables ) delete g_dConsoleVariables.Current();
    HASH_FOREACH( g_dConsoleCommands  ) delete g_dConsoleCommands.Current();
    g_dConsoleVariables.Delete();
    g_dConsoleCommands.Delete();
}

bool ConsoleValidate( ConsoleCommand_c * pCmd, ConsoleMessage_c * pMsg )
{
    Assert( pCmd && pMsg );

    if( pMsg->GetNumArgs() < pCmd->GetNumArgs() )
    {
        g_cLogStdOut.Out( "too few arguments for command [%s]. got %i, need %i\n"
                        , pCmd->m_sName
                        , pMsg->GetNumArgs()
                        , pCmd->GetNumArgs()
                        );
        return false;
    }

    for( int i = 0; i < pCmd->GetNumArgs(); i++ )
        if( pMsg->GetArgType(i) != pCmd->GetArgType(i) )
        {
            g_cLogStdOut.Out( "invalid parameter %i type for command [%s]. got %s, need %s\n"
                            , i
                            , pCmd->m_sName
                            , pMsg->GetArgType(i) == CONSOLEARG_NUMBER ? "number" : "string"
                            , pCmd->GetArgType(i) == CONSOLEARG_NUMBER ? "number" : "string"
                            );
            return false;
        }

    return true;
}

bool ConsoleExec( ConsoleCommand_c * pCmd, ConsoleMessage_c * pMsg )
{
    Assert( pCmd && pMsg );

    if( ConsoleValidate( pCmd, pMsg ) )
    {
        pCmd->Execute( pMsg );
        return true;
    }
    return false;
}

static char * TokenFirst ( char * szString, char & buf )
{
    const char seps[] = " ;\n\t\r";
    const char tokenseps[] = ";\n";

    char * token = szString;
    while ( *token && strcspn ( token, seps ) == 0 )
    {
        // separate character tokens
        if ( strcspn ( token, tokenseps ) == 0 )
        {
            buf = token[1];
            token[1] = 0;
            return token;
        }
        token++;
    }
    char * end = token;

    if ( *token == '"' )
    {
        end++;
        while ( *end && *(end++) != '"' );
    }
    else if ( *token == '\'' )
    {
        end++;
        while ( *end && *(end++) != '\'' );
    }
    else
        end = token + strcspn ( token, seps );

    buf = *end;
    *end = 0;

    return *token ? token : NULL;
}

static char * TokenNext ( char * szString, char & buf )
{
    if ( buf == 0 )
        return NULL;

    char * next = szString + strlen ( szString );
    *next = buf;
    return TokenFirst ( next, buf );
}

static void TokenReplace ( char * & szPassString, char * & szToken, const char * szString, char & buf )
{
    int iTokenLen   = (int) strlen ( szToken );
    int iTokenPos   = (int) ( szToken - szPassString );
    szToken[iTokenLen] = buf;
    int iPassLen    = (int) strlen ( szPassString );
    int iStringLen  = (int) strlen ( szString );

    char * newstr = new char [ iPassLen + iStringLen - iTokenLen + 1 ];

    memcpy ( newstr, szPassString, iTokenPos );
    memcpy ( newstr+iTokenPos, szString, iStringLen );
    memcpy ( newstr+iTokenPos+iStringLen, szPassString+iTokenPos+iTokenLen, iPassLen-iTokenPos-iTokenLen+1 );

    delete szPassString;
    szPassString = newstr;
    szToken = newstr + iTokenPos;
    buf = *szToken;
    *szToken = 0;
}

bool ConsoleExec ( const char * sParseString )
{
    char * passString = new char[strlen(sParseString)+1];

    ConsoleCommand_c *  pCommand = NULL;
    ConsoleMessage_c    cMessage;

    strcpy( passString, sParseString );

    bool bRet = true;

    // let's parse
    char buf;
    char * token = TokenFirst ( passString, buf );

    while( token )
    {
        // if it's a command execution request
        if ( *token==';' || *token=='\n' )
        {
            if( pCommand )
                if( !ConsoleExec( pCommand, &cMessage ) )
                    bRet = false;
            pCommand = NULL;
            cMessage.Reset();
        }
        // if there wasn't command yet
        else if( !pCommand )
        {
            if( *token=='#' )
                return true;
            pCommand = ConsoleGetCommand( token );
            if( !pCommand )
            {
                // see if it's a shortcut
                ConsoleVariable_c * pVariable = ConsoleGetVariable( token );
                if( pVariable && pVariable->GetType() == CONSOLEARG_STRING )
                {
                    TokenReplace ( passString, token, pVariable->GetString(), buf );
                }
                else
                {
                    g_cLogStdOut.Out( "unknown command or variable [%s] in [%s]\n", token, sParseString );
                    return false;
                }
            }
        }
        else
        {
            if( cMessage.GetNumArgs() >= MAX_CONSOLE_MESSAGE_ARGUMENTS )
            {
                g_cLogStdOut.Out( "too many arguments in [%s]\n", sParseString );
                return false;
            }

            // see if it's a number
            if( isdigit(*token) || *token=='.' || *token=='-' || *token=='+' )
            {
                EQUFLT fVal = atof( token );
                cMessage.AddArg( fVal );
            }
            // see if it's a variable
            else if( isalpha(*token) || *token=='_' )
            {
                ConsoleVariable_c * pVar = ConsoleGetVariable( token );
                if( !pVar )
                {
                    // add string argument
                    cMessage.AddArg( token );
                }
                else
                {
                    // add variable's value
                    if( pVar->GetType() == CONSOLEARG_NUMBER )
                        cMessage.AddArg( pVar->GetDouble() );
                    else
                        cMessage.AddArg( pVar->GetString() );
                }
            }
            // see if it's a quoted string
            else if ( *token == '"' )
            {
                token[strlen(token)-1] = 0;
                cMessage.AddArg ( token+1 );
                token[strlen(token)-1] = '"';
            }
            else if ( *token == '\'' )
            {
                token[strlen(token)-1] = 0;
                cMessage.AddArg ( token+1 );
                token[strlen(token)-1] = '\'';
            }
            else
            {
                g_cLogStdOut.Out( "invalid syntax [%s] in [%s]\n", token, sParseString );
            }
        }

        // next token
        token = TokenNext ( token, buf );
    }

    if( pCommand )
        if( !ConsoleExec( pCommand, &cMessage ) )
            bRet = false;

    delete passString;

    return bRet;
}

void ConsoleProcessInput()
{
    if( !g_bPrompt )
    {
        printf( "%s", g_sPrompt );
        g_bPrompt = true;
    }
    if( kbhit() )
    {
        int c = getch();
        if( isprint( c & 0xFF ) )
        {
            g_sConsoleInput[g_nConsoleInput] = c;
            if( g_nConsoleInput < MAX_CONSOLE_STRING_LENGTH-1 )
                g_nConsoleInput++;
            else
                printf( "\nmax console string length exceeded\n" );
            putc( c, stdout );
        }
        else if( c == '\b' )
        {
            if( g_nConsoleInput )
            {
                g_nConsoleInput--;
                printf( "\b \b" );
            }
        }
        else if( c == '\r' )
        {
            printf( "\n" );
            g_sConsoleInput[g_nConsoleInput] = 0;
            g_cLogMain.PureOut( "%s%s\n", g_sPrompt, g_sConsoleInput );
            g_cLogMain.Flush();
            ConsoleExec( g_sConsoleInput );
            g_cLogMain.Flush();
            g_nConsoleInput = 0;
            g_bPrompt = false;
        }
        // function or array key is coded by two characters
        else if( c==0 || c==0xE0 )
        {
            getch();
        }
    }
}

int CompareCommands( const void * pItem1, const void * pItem2 )
{
    ConsoleCommand_c * pCmd1 = *(ConsoleCommand_c**) pItem1;
    ConsoleCommand_c * pCmd2 = *(ConsoleCommand_c**) pItem2;
    return strcmp( pCmd1->m_sName, pCmd2->m_sName );
}

int CompareVariables( const void * pItem1, const void * pItem2 )
{
    ConsoleVariable_c * pVar1 = *(ConsoleVariable_c**) pItem1;
    ConsoleVariable_c * pVar2 = *(ConsoleVariable_c**) pItem2;
    return strcmp( pVar1->m_sName, pVar2->m_sName );
}

void ConsoleReportCommands( Log_c & cLog, const char * szPrefix )
{
    DynamicArray_T< ConsoleCommand_c * > dCommands;
    int iMaxNameLength = 0;
    int iMaxArgsLength = 0;

    HASH_FOREACH( g_dConsoleCommands )
    {
        iMaxNameLength = max( iMaxNameLength, (int)strlen( g_dConsoleCommands.Current()->m_sName ) );
        iMaxArgsLength = max( iMaxArgsLength, g_dConsoleCommands.Current()->GetNumArgs() );
        dCommands.Add( g_dConsoleCommands.Current() );
    }

    dCommands.Sort( CompareCommands );

    ARRAY_FOREACH( i, dCommands )
        if( strstr( dCommands[i]->m_sName, szPrefix ) == dCommands[i]->m_sName )
        {
            ConsoleString_t sArgs;
            for( int j = 0; j < dCommands[i]->GetNumArgs(); j++ )
                sArgs[j] = dCommands[i]->GetArgType(j) == CONSOLEARG_NUMBER ? 'n' : 's';
            sArgs[dCommands[i]->GetNumArgs()] = 0;
            cLog.Out( "%-*s  %-*s  %s\n"
                    , iMaxNameLength
                    , dCommands[i]->m_sName 
                    , iMaxArgsLength
                    , sArgs
                    , dCommands[i]->m_sHelpInfo
                    );
        }
}

void ConsoleReportVariables( Log_c & cLog, const char * szPrefix )
{
    DynamicArray_T< ConsoleVariable_c * > dVariables;
    int iMaxLength = 0;

    HASH_FOREACH( g_dConsoleVariables )
    {
        iMaxLength = max( iMaxLength, (int)strlen(g_dConsoleVariables.Current()->m_sName) );
        dVariables.Add( g_dConsoleVariables.Current() );
    }

    dVariables.Sort( CompareVariables );

    ARRAY_FOREACH( i, dVariables )
        if( strstr( dVariables[i]->m_sName, szPrefix ) == dVariables[i]->m_sName )
        {
            cLog.Out( "%-*s  "
                    , iMaxLength
                    , dVariables[i]->m_sName 
                    );
                if( dVariables[i]->GetType() == CONSOLEARG_STRING )
                    cLog.Out( "\"%s\"\n"
                            , dVariables[i]->GetString()
                            );
                else
                    cLog.Out( "% e\n"
                            , dVariables[i]->GetDouble()
                            );
        }
}

void ConsoleDumpVariables( Log_c & cLog, const char * szPrefix )
{
    HASH_FOREACH( g_dConsoleVariables )
        if( strstr( g_dConsoleVariables.Current()->m_sName, szPrefix ) 
        ==          g_dConsoleVariables.Current()->m_sName )
        {
            ConsoleVariable_c * pVar = g_dConsoleVariables.Current();
            if( pVar->GetType() == CONSOLEARG_STRING )
                cLog.Out( "sets \"%s\" \"%s\"\n"
                        , pVar->m_sName
                        , pVar->GetString()
                        );
            else
                cLog.Out( "setf \"%s\" %e\n"
                        , pVar->m_sName
                        , pVar->GetDouble()
                        );
        }
}

/////////////////////////////////////////////////////////////////////////////////////

///
/// $Id: console.cpp,v 1.6 2006/06/12 20:14:40 olegus Exp $
///

